Программирование сетевых приложений

Типы исключительных ситуаций и процесс их обработки в C++ и Qt

Программирование сетевых приложений

Содержание лекции

  • Введение в исключительные ситуации
  • Необходимость обработки исключений
  • Основные принципы обработки исключений
  • Типы исключений
  • Обработка исключительных ситуаций
  • Объекты исключительных ситуаций
  • Стандартные исключительные ситуации
  • Пользовательские исключения
  • Особенности обработки исключений в Qt
  • Лучшие практики
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Введение в исключительные ситуации

Исключительные ситуации (exceptions) являются критически важным механизмом обработки ошибок в современном программировании. Они позволяют разработчикам создавать надежные и устойчивые приложения, способные корректно реагировать на непредвиденные ситуации во время выполнения программы.

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Необходимость обработки исключительных ситуаций

Проблемы традиционной обработки ошибок

До появления исключений обработка ошибок в C++ основывалась на следующих подходах:

  1. Коды возврата функций
    int result = someFunction();
    if (result != 0) {
        // Обработка ошибки
    }
    
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
  1. Глобальные переменные состояния

    errno = 0;
    FILE* file = fopen("data.txt", "r");
    if (errno != 0) {
        // Обработка ошибки
    }
    
  2. Флаги ошибок

    class MyClass {
    private:
        bool errorFlag;
        std::string errorMessage;
    public:
        bool hasError() const { return errorFlag; }
    };
    
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Преимущества исключений

Исключения решают многие проблемы традиционных подходов:

  1. Разделение кода основной логики и обработки ошибок
  2. Принудительная обработка критических ошибок
  3. Возможность передачи ошибок через несколько уровней стека вызовов
  4. Гарантированная очистка ресурсов (RAII)
  5. Типобезопасность при обработке различных типов ошибок
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Основные принципы обработки исключений

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

1. Принцип RAII (Resource Acquisition Is Initialization)

class FileHandler {
private:
    FILE* file;
    
public:
    FileHandler(const std::string& filename) {
        file = fopen(filename.c_str(), "r");
        if (!file) {
            throw std::runtime_error("Не удалось открыть файл");
        }
    }
    
    ~FileHandler() {
        if (file) {
            fclose(file);
        }
    }
    
    // Запрет копирования
    FileHandler(const FileHandler&) = delete;
    FileHandler& operator=(const FileHandler&) = delete;
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

2. Принцип безопасности исключений

Базовая гарантия (Basic Guarantee): Программа остается в согласованном состоянии, не происходит утечек ресурсов.

Сильная гарантия (Strong Guarantee): Операция либо завершается успешно, либо оставляет программу в исходном состоянии.

Гарантия отсутствия исключений (No-throw Guarantee): Операция гарантированно не выбрасывает исключений.

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

3. Принцип специфичности

Обрабатывайте конкретные типы исключений, а не общие:

// Плохо
try {
    // Код
} catch (...) {
    // Слишком общо
}

// Хорошо
try {
    // Код
} catch (const std::invalid_argument& e) {
    // Обработка конкретной ошибки
} catch (const std::out_of_range& e) {
    // Обработка другой конкретной ошибки
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Типы исключений

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

1. Стандартные исключения C++

Стандартная библиотека C++ предоставляет иерархию исключений:

#include <stdexcept>
#include <iostream>

// Базовый класс для всех стандартных исключений
std::exception
├── std::logic_error
│   ├── std::invalid_argument
│   ├── std::domain_error
│   ├── std::length_error
│   └── std::out_of_range
├── std::runtime_error
│   ├── std::range_error
│   ├── std::overflow_error
│   └── std::underflow_error
└── std::bad_alloc
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

2. Примеры стандартных исключений

#include <stdexcept>
#include <vector>
#include <memory>

void demonstrateStandardExceptions() {
    try {
        // std::invalid_argument
        int parseInt(const std::string& str) {
            if (str.empty()) {
                throw std::invalid_argument("Пустая строка");
            }
            return std::stoi(str);
        }
        
        // std::out_of_range
        std::vector<int> vec = {1, 2, 3};
        int value = vec.at(10); // Выбросит out_of_range
        
        // std::bad_alloc
        try {
            int* hugeArray = new int[1000000000000];
        } catch (const std::bad_alloc& e) {
            std::cerr << "Ошибка выделения памяти: " << e.what() << std::endl;
        }
        
    } catch (const std::exception& e) {
        std::cerr << "Стандартное исключение: " << e.what() << std::endl;
    }
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Обработка исключительных ситуаций

Базовый синтаксис

try {
    // Код, который может выбросить исключение
} catch (const ExceptionType& e) {
    // Обработка исключения типа ExceptionType
} catch (...) {
    // Обработка всех остальных исключений
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Продвинутые техники обработки

1. Повторное выбрасывание исключений

void processData() {
    try {
        // Некоторая обработка
        riskyOperation();
    } catch (const std::exception& e) {
        // Логирование ошибки
        logError(e.what());
        // Повторное выбрасывание для вышестоящей обработки
        throw;
    }
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

2. Спецификация исключений (устарело в C++11)

// Старый стиль (не рекомендуется)
void oldStyleFunction() throw(std::runtime_error) {
    // Может выбросить только std::runtime_error
}

// Современный стиль
void modernFunction() noexcept {
    // Гарантированно не выбрасывает исключений
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

3. Группировка исключений

template<typename T>
class Container {
public:
    void insert(const T& value) {
        try {
            // Проверка и вставка
            if (value < 0) {
                throw std::invalid_argument("Отрицательное значение");
            }
            if (data_.size() >= max_size_) {
                throw std::length_error("Превышен максимальный размер");
            }
            data_.push_back(value);
        } catch (const std::logic_error& e) {
            // Обработка всех логических ошибок
            std::cerr << "Логическая ошибка: " << e.what() << std::endl;
            throw;
        }
    }
    
private:
    std::vector<T> data_;
    size_t max_size_ = 1000;
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Объекты исключительных ситуаций

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Создание собственных классов исключений

#include <string>
#include <chrono>

class NetworkException : public std::runtime_error {
private:
    std::string host_;
    int port_;
    std::chrono::system_clock::time_point timestamp_;
    
public:
    NetworkException(const std::string& message, 
                     const std::string& host, 
                     int port)
        : std::runtime_error(message),
          host_(host),
          port_(port),
          timestamp_(std::chrono::system_clock::now()) {}
    
    const std::string& getHost() const { return host_; }
    int getPort() const { return port_; }
    
    std::chrono::system_clock::time_point getTimestamp() const {
        return timestamp_;
    }
    
    std::string getDetailedMessage() const {
        return what() + std::string(" (") + host_ + ":" + 
               std::to_string(port_) + ")";
    }
};

// Использование
void connectToServer(const std::string& host, int port) {
    // Симуляция ошибки подключения
    bool connectionFailed = true;
    
    if (connectionFailed) {
        throw NetworkException("Не удалось подключиться к серверу", 
                              host, port);
    }
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Иерархия пользовательских исключений

// Базовый класс для всех исключений приложения
class ApplicationException : public std::exception {
protected:
    std::string message_;
    int errorCode_;
    
public:
    ApplicationException(const std::string& message, int errorCode = 0)
        : message_(message), errorCode_(errorCode) {}
    
    const char* what() const noexcept override {
        return message_.c_str();
    }
    
    int getErrorCode() const { return errorCode_; }
};

// Исключения для различных подсистем
class DatabaseException : public ApplicationException {
private:
    std::string query_;
    
public:
    DatabaseException(const std::string& message, 
                     const std::string& query, 
                     int errorCode = 0)
        : ApplicationException(message, errorCode), query_(query) {}
    
    const std::string& getQuery() const { return query_; }
};

class SecurityException : public ApplicationException {
private:
    std::string userId_;
    std::string operation_;
    
public:
    SecurityException(const std::string& message, 
                     const std::string& userId, 
                     const std::string& operation)
        : ApplicationException(message), userId_(userId), operation_(operation) {}
    
    const std::string& getUserId() const { return userId_; }
    const std::string& getOperation() const { return operation_; }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Стандартные исключительные ситуации

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>

void demonstrateCommonExceptions() {
    try {
        // std::bad_alloc - нехватка памяти
        try {
            size_t hugeSize = std::numeric_limits<size_t>::max();
            int* array = new int[hugeSize];
        } catch (const std::bad_alloc& e) {
            std::cout << "bad_alloc: " << e.what() << std::endl;
        }
        
        // std::out_of_range
        try {
            std::vector<int> vec = {1, 2, 3};
            int value = vec.at(100); // Выход за границы
        } catch (const std::out_of_range& e) {
            std::cout << "out_of_range: " << e.what() << std::endl;
        }
        
        // std::invalid_argument
        try {
            std::stoi("не число");
        } catch (const std::invalid_argument& e) {
            std::cout << "invalid_argument: " << e.what() << std::endl;
        }
        
        // std::overflow_error
        try {
            int result = std::accumulate(std::vector<int>(1000000, 1000000).begin(),
                                       std::vector<int>(1000000, 1000000).end(), 0);
        } catch (const std::overflow_error& e) {
            std::cout << "overflow_error: " << e.what() << std::endl;
        }
        
    } catch (const std::exception& e) {
        std::cout << "Общее исключение: " << e.what() << std::endl;
    }
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Определение и порождение собственных исключительных ситуаций

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Создание иерархии исключений для сетевого приложения

#include <exception>
#include <string>
#include <system_error>

// Базовое исключение для сетевых операций
class NetworkException : public std::runtime_error {
protected:
    int errorCode_;
    std::string details_;
    
public:
    NetworkException(const std::string& message, int errorCode = 0)
        : std::runtime_error(message), errorCode_(errorCode) {}
    
    int getErrorCode() const { return errorCode_; }
    virtual std::string getDetails() const { return what(); }
};

// Исключения для различных сетевых ситуаций
class ConnectionException : public NetworkException {
private:
    std::string host_;
    int port_;
    
public:
    ConnectionException(const std::string& host, int port, int errorCode = 0)
        : NetworkException("Ошибка подключения к " + host + ":" + 
                          std::to_string(port), errorCode),
          host_(host), port_(port) {}
    
    std::string getDetails() const override {
        return "Не удалось подключиться к " + host_ + ":" + 
               std::to_string(port) + " (код ошибки: " + 
               std::to_string(errorCode_) + ")";
    }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
class DataTransmissionException : public NetworkException {
private:
    size_t bytesSent_;
    size_t bytesTotal_;
    
public:
    DataTransmissionException(size_t sent, size_t total, int errorCode = 0)
        : NetworkException("Ошибка передачи данных", errorCode),
          bytesSent_(sent), bytesTotal_(total) {}
    
    std::string getDetails() const override {
        return "Передано " + std::to_string(bytesSent_) + 
               " из " + std::to_string(bytesTotal_) + 
               " байт (код ошибки: " + std::to_string(errorCode_) + ")";
    }
};

class ProtocolException : public NetworkException {
private:
    std::string protocol_;
    std::string violation_;
    
public:
    ProtocolException(const std::string& protocol, 
                     const std::string& violation, 
                     int errorCode = 0)
        : NetworkException("Нарушение протокола " + protocol, errorCode),
          protocol_(protocol), violation_(violation) {}
    
    std::string getDetails() const override {
        return "Протокол " + protocol_ + ": " + violation_;
    }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Использование пользовательских исключений

class NetworkClient {
private:
    std::string host_;
    int port_;
    bool connected_;
    
public:
    NetworkClient(const std::string& host, int port)
        : host_(host), port_(port), connected_(false) {}
    
    void connect() {
        try {
            // Симуляция подключения
            bool connectionSuccessful = false; // Симулируем ошибку
            
            if (!connectionSuccessful) {
                throw ConnectionException(host_, port_, 1001);
            }
            
            connected_ = true;
            
        } catch (const ConnectionException& e) {
            std::cerr << "Ошибка подключения: " << e.getDetails() << std::endl;
            // Попытка переподключения или другие действия
            throw;
        }
    }
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
    void sendData(const std::vector<uint8_t>& data) {
        if (!connected_) {
            throw std::logic_error("Клиент не подключен");
        }
        
        try {
            // Симуляция отправки данных
            size_t sent = 0;
            size_t total = data.size();
            
            // Симулируем частичную отправку с ошибкой
            bool transmissionFailed = true;
            
            if (transmissionFailed) {
                throw DataTransmissionException(sent, total, 2001);
            }
            
        } catch (const DataTransmissionException& e) {
            std::cerr << "Ошибка передачи: " << e.getDetails() << std::endl;
            
            // Попытка восстановления соединения
            try {
                reconnect();
                // Повторная отправка
            } catch (const ConnectionException& reconnectError) {
                std::cerr << "Не удалось восстановить соединение: " 
                         << reconnectError.what() << std::endl;
                throw;
            }
        }
    }
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
private:
    void reconnect() {
        connected_ = false;
        // Логика переподключения
        connect();
    }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Особенности обработки исключений в Qt

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Использование исключений в Qt-приложениях

#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
#include <QException>
#include <QDebug>

// Qt-ориентированное исключение
class QtNetworkException : public QException {
private:
    QString message_;
    int errorCode_;
    
public:
    QtNetworkException(const QString& message, int errorCode = 0)
        : message_(message), errorCode_(errorCode) {}
    
    void raise() const override { throw *this; }
    QtNetworkException* clone() const override { 
        return new QtNetworkException(*this); 
    }
    
    const char* what() const noexcept override {
        return message_.toLocal8Bit().constData();
    }
    
    QString message() const { return message_; }
    int errorCode() const { return errorCode_; }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
// Класс для безопасной работы с сетевыми операциями в Qt
class SafeNetworkManager : public QObject {
    Q_OBJECT
    
private:
    QNetworkAccessManager* manager_;
    
public:
    explicit SafeNetworkManager(QObject* parent = nullptr)
        : QObject(parent), manager_(new QNetworkAccessManager(this)) {}
    
    void makeSafeRequest(const QUrl& url) {
        try {
            QNetworkRequest request(url);
            QNetworkReply* reply = manager_->get(request);
            
            // Используем лямбда-функцию для обработки ответа
            connect(reply, &QNetworkReply::finished, [reply, this]() {
                try {
                    handleReply(reply);
                } catch (const QtNetworkException& e) {
                    qWarning() << "Ошибка сети:" << e.message();
                    emit errorOccurred(e.message(), e.errorCode());
                } catch (const std::exception& e) {
                    qWarning() << "Стандартное исключение:" << e.what();
                    emit errorOccurred(QString::fromLocal8Bit(e.what()), -1);
                }
                
                reply->deleteLater();
            });
            
        } catch (const QtNetworkException& e) {
            qWarning() << "Исключение при создании запроса:" << e.message();
            emit errorOccurred(e.message(), e.errorCode());
        }
    }
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
signals:
    void errorOccurred(const QString& message, int errorCode);
    void dataReceived(const QByteArray& data);
    
private:
    void handleReply(QNetworkReply* reply) {
        if (reply->error() != QNetworkReply::NoError) {
            throw QtNetworkException(
                reply->errorString(), 
                static_cast<int>(reply->error())
            );
        }
        
        QByteArray data = reply->readAll();
        emit dataReceived(data);
    }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Интеграция исключений с Qt сигналами и слотами

#include <QObject>
#include <QTimer>
#include <QException>

// Класс для демонстрации обработки исключений в Qt событиях
class ExceptionSafeWorker : public QObject {
    Q_OBJECT
    
private:
    QTimer* workTimer_;
    int operationCount_;
    
public:
    explicit ExceptionSafeWorker(QObject* parent = nullptr)
        : QObject(parent), operationCount_(0) {
        
        workTimer_ = new QTimer(this);
        connect(workTimer_, &QTimer::timeout, this, &ExceptionSafeWorker::performWork);
    }
    
    void startWork() {
        workTimer_->start(1000); // Работа каждую секунду
    }
    
    void stopWork() {
        workTimer_->stop();
    }
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
private slots:
    void performWork() {
        try {
            operationCount_++;
            
            // Симуляция работы с потенциальными исключениями
            if (operationCount_ % 3 == 0) {
                throw std::runtime_error("Симулированная ошибка в работе");
            }
            
            if (operationCount_ % 5 == 0) {
                throw QtNetworkException("Сетевая ошибка в работе", 500);
            }
            
            // Успешная операция
            qDebug() << "Операция" << operationCount_ << "выполнена успешно";
            emit workCompleted(operationCount_);
            
        } catch (const QtNetworkException& e) {
            qWarning() << "Сетевая ошибка в операции" << operationCount_ 
                      << ":" << e.message();
            emit workFailed(operationCount_, e.message());
            
        } catch (const std::exception& e) {
            qWarning() << "Ошибка в операции" << operationCount_ 
                      << ":" << e.what();
            emit workFailed(operationCount_, QString::fromLocal8Bit(e.what()));
            
        } catch (...) {
            qCritical() << "Неизвестная ошибка в операции" << operationCount_;
            emit workFailed(operationCount_, "Неизвестная ошибка");
        }
    }
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
signals:
    void workCompleted(int operationId);
    void workFailed(int operationId, const QString& error);
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Лучшие практики обработки исключений

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

1. Использование RAII для управления ресурсами

class DatabaseConnection {
private:
    // Использование умных указателей для автоматического управления ресурсами
    std::unique_ptr<sqlite3, decltype(&sqlite3_close)> db_;
    
public:
    DatabaseConnection(const std::string& dbPath) 
        : db_(nullptr, sqlite3_close) {
        
        sqlite3* rawDb = nullptr;
        int result = sqlite3_open(dbPath.c_str(), &rawDb);
        
        if (result != SQLITE_OK) {
            std::string error = sqlite3_errmsg(rawDb);
            sqlite3_close(rawDb);
            throw std::runtime_error("Не удалось открыть БД: " + error);
        }
        
        db_.reset(rawDb);
    }
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
    void executeQuery(const std::string& query) {
        char* errorMsg = nullptr;
        int result = sqlite3_exec(db_.get(), query.c_str(), 
                                nullptr, nullptr, &errorMsg);
        
        if (result != SQLITE_OK) {
            std::string error(errorMsg);
            sqlite3_free(errorMsg);
            throw std::runtime_error("Ошибка SQL: " + error);
        }
    }
    
    // Автоматическое закрытие соединения при разрушении объекта
    ~DatabaseConnection() = default;
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

2. Создание безопасных интерфейсов

// Безопасный класс для работы с файлами
class SafeFile {
private:
    std::FILE* file_;
    std::string filename_;
    
public:
    SafeFile(const std::string& filename, const std::string& mode)
        : filename_(filename) {
        
        file_ = std::fopen(filename.c_str(), mode.c_str());
        if (!file_) {
            throw std::runtime_error("Не удалось открыть файл: " + filename);
        }
    }
    
    // Запрет копирования
    SafeFile(const SafeFile&) = delete;
    SafeFile& operator=(const SafeFile&) = delete;
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
    // Разрешение перемещения
    SafeFile(SafeFile&& other) noexcept 
        : file_(other.file_), filename_(std::move(other.filename_)) {
        other.file_ = nullptr;
    }
    
    SafeFile& operator=(SafeFile&& other) noexcept {
        if (this != &other) {
            close();
            file_ = other.file_;
            filename_ = std::move(other.filename_);
            other.file_ = nullptr;
        }
        return *this;
    }
    
    void write(const std::string& data) {
        if (!file_) {
            throw std::logic_error("Файл не открыт");
        }
        
        size_t written = std::fwrite(data.c_str(), 1, data.size(), file_);
        if (written != data.size()) {
            throw std::runtime_error("Ошибка записи в файл");
        }
    }
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
    std::string read(size_t maxSize = 1024) {
        if (!file_) {
            throw std::logic_error("Файл не открыт");
        }
        
        std::vector<char> buffer(maxSize);
        size_t read = std::fread(buffer.data(), 1, maxSize, file_);
        
        if (std::ferror(file_)) {
            throw std::runtime_error("Ошибка чтения файла");
        }
        
        return std::string(buffer.data(), read);
    }
    
    ~SafeFile() {
        close();
    }
    
private:
    void close() noexcept {
        if (file_) {
            std::fclose(file_);
            file_ = nullptr;
        }
    }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

3. Использование современных возможностей C++

#include <optional>
#include <variant>
#include <expected>

// Использование std::optional для безопасного возврата значений
std::optional<int> safeDivide(int a, int b) {
    if (b == 0) {
        return std::nullopt; // Вместо исключения
    }
    return a / b;
}

// Использование std::variant для возврата различных типов результатов
using Result = std::variant<int, std::string>; // Успех или сообщение об ошибке

Result calculate(int a, int b) {
    if (b == 0) {
        return std::string("Деление на ноль");
    }
    return a / b;
}

// Использование std::expected (C++23)
std::expected<int, std::string> safeCalculation(int a, int b) {
    if (b == 0) {
        return std::unexpected("Деление на ноль");
    }
    return a / b;
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Заключение

Правильная обработка исключений является основой создания надежных и устойчивых приложений на C++. Использование современных подходов, таких как RAII, создание собственных иерархий исключений и интеграция с фреймворками вроде Qt, позволяет создавать качественный код, способный корректно реагировать на непредвиденные ситуации.

Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Ключевые принципы:

  • Используйте исключения для исключительных ситуаций, а не для обычного потока управления
  • Создавайте специфичные типы исключений для различных типов ошибок
  • Обеспечивайте безопасность исключений через RAII
  • Логируйте исключения для последующего анализа
  • Тестируйте обработку исключений в вашем коде
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений

Вопросы для самопроверки

  1. Какие три уровня гарантии безопасности исключений существуют?
  2. Чем отличается std::logic_error от std::runtime_error?
  3. Как правильно создавать собственные иерархии исключений?
  4. Какие преимущества дает использование RAII в обработке исключений?
  5. Как интегрировать исключения с Qt сигналами и слотами?
  6. Что такое noexcept и когда его следует использовать?
  7. Какие современные альтернативы исключениям доступны в C++?
  8. Как правильно повторно выбрасывать исключения?
Типы исключительных ситуаций и процесс их обработки

Заметки докладчика: - Лекция посвящена обработке исключительных ситуаций в C++ и Qt с акцентом на сетевое программирование - Связь с лабораторной работой: при парсинге сетевых данных (HTTP-ответы, JSON, бинарные протоколы) ошибки неизбежны — нужно уметь их обрабатывать корректно - Основной фокус: не только синтаксис try/catch, а проектирование устойчивых сетевых приложений - В конце лекции студенты должны уметь создавать собственные иерархии исключений для сетевого приложения

Заметки докладчика: - В сетевом программировании ошибки — не исключение, а норма: обрывы соединения, таймауты, неверные данные от сервера, DNS-ошибки - Без исключений каждый вызов send()/recv()/connect() нужно проверять вручную — код превращается в «лесенку» из if-else - Коды возврата (errno, HRESULT) легко проигнорировать — компилятор не заставит проверить; исключения невозможно проигнорировать - Привести пример: функция connect() возвращает -1 при ошибке, но если программист забыл проверить — неопределённое поведение при следующем send()

Заметки докладчика: - Ключевое преимущество исключений в C++ — RAII (Resource Acquisition Is Initialization) - При выбросе исключения происходит раскрутка стека (stack unwinding): для каждого локального объекта вызывается деструктор - Это значит: открытый сокет, файл, блокировка мьютекса — всё закроется/освободится автоматически - Без RAII программисту пришлось бы вручную закрывать ресурсы в каждом catch-блоке — легко забыть, что ведёт к утечкам - Пример: если в функции openConnection() выброшено исключение после socket(), деструктор обёртки вызовет close() автоматически

Заметки докладчика: - RAII — самый важный паттерн C++, студенты должны его понимать на уровне рефлексии - Каждый сетевой ресурс (сокет, TCP-соединение, SSL-контекст, файловый дескриптор) должен быть обёрнут в RAII-класс - std::unique_ptr с кастомным deleter — простой способ обернуть C-ресурс (например, socket → close, FILE* → fclose) - Qt-классы (QTcpSocket, QFile) сами по себе RAII-обёртки — при разрушении объекта ресурс освобождается - На практике: если в функции 5 точек, где может произойти ошибка — без RAII нужно 5 раз писать очистку, с RAII — ноль раз

Заметки докладчика: - std::exception — базовый класс для всех стандартных исключений, всегда ловите через const std::exception& - Для сетевых ошибок чаще всего подходит std::runtime_error — ошибки, которые нельзя предвидеть при компиляции (обрыв связи, таймаут) - std::logic_error — ошибки программиста (неверный аргумент, выход за границы), можно отловить при тестировании - ВАЖНО: ловить исключения только по константной ссылке (const std::exception& e) — иначе будет срезка объекта и потенциальное двойное удаление - catch (...) — использовать только как последний рубеж, для логирования и корректного завершения

Заметки докладчика: - В реальных приложениях создавайте иерархию: NetworkException → ConnectionException, TimeoutException, ProtocolException, DataTransmissionException - Каждое исключение должно хранить контекст: host, port, timestamp, код ошибки ОС (errno/WSAGetLastError()) — это критично для отладки - Переопределяйте what() и добавляйте getDetails() для структурированного вывода в лог - Рассмотреть пример: при парсинге HTTP-ответа — ProtocolException с указанием ожидаемого и полученного HTTP-статуса - Не перегружайте исключения лишними данными — объект копируется при throw, большие строки могут замедлить раскрутку стека

Заметки докладчика: - У Qt ограниченная поддержка исключений: исключения НЕ пересекают границы сигналов/слотов при использовании queued connections (между потоками) - QException требует реализации raise() и clone() — это необходимо для корректного перехвата через QFuture/QPromise - На практике многие Qt-разработчики предпочитают сигналы об ошибках (errorOccurred) и коды возврата вместо исключений - В асинхронном Qt-коде (QNetworkAccessManager) исключения можно использовать только внутри синхронных обработчиков ответа - Рекомендация для лабораторных: в синхронных операциях (парсинг, валидация) — исключения, в асинхронных (сетевые запросы) — сигналы об ошибках

Заметки докладчика — ожидаемые ответы: 1. Базовая гарантия (без утечек), сильная гарантия (commit-or-rollback), гарантия отсутствия исключений (noexcept) 2. logic_error — ошибки программиста (предсказуемы), runtime_error — ошибки окружения (непредсказуемы при компиляции) 3. Наследовать от std::exception или std::runtime_error, переопределить what(), обеспечить виртуальный деструктор 4. Деструкторы вызываются автоматически при раскрутке стека — ресурсы (сокеты, файлы, мьютексы) освобождаются без ручного кода 5. Оборачивать тело слота в try/catch, при ошибке генерировать сигнал об ошибке; не пробрасывать через queued connections 6. noexcept указывает, что функция не выбрасывает исключений; использовать для деструкторов, move-операций, критичных к производительности функций 7. std::optional (нет значения), std::variant (типобезопасный union), std::expected (C++23 — значение или ошибка) 8. Использовать throw; (без аргумента) для сохранения оригинального типа исключения и стек-трейса; throw e создаст копию и может потерять тип